package xyz.app.accessors;

import jakarta.persistence.EntityTransaction;
import lombok.NonNull;
import xyz.app.Wrapper;
import xyz.app.dependencies.HasDomainMembers;
import xyz.app.AbstractDomain;
import xyz.app.logic.PersistenceLogic;

import java.util.Objects;
import java.util.Optional;

public interface DbCreator<T extends AbstractDomain> extends DbAccessor<T> {

    default Wrapper save(@NonNull Wrapper dto) {

        if (!Objects.equals(dto.domainClass(), domainClass())) return null;

        saveMembers(dto);

        return dto.unwrap(domainClass())
                .map(this::buildPersistenceLogic)
                .map(PersistenceLogic::getOutput)
                .map(this::doSave)
                .map(Wrapper::wrap)
                .orElse(null);
    }

    private void saveMembers(@NonNull Wrapper dto) {

        if (!(this instanceof HasDomainMembers<?> hasMembers)) return;
        info("Domain potentially has members that must be saved first, [{}]", dto);

        for (var accessor : hasMembers.memberAccessors())
            Optional.ofNullable(accessor.get(dto))
                    .ifPresentOrElse(member -> daoRegistry().getDao(member.domainClass())
                                    .map(dao -> dao.save(member))
                                    .ifPresentOrElse(
                                            value -> accessor.set(dto, value),
                                            () -> accessor.set(dto, null)),

                            () -> accessor.set(dto, null));
    }

    private T doSave(@NonNull T t) {
        var manager = getEmf().createEntityManager();

        try (manager) {

            manager.getTransaction().begin();

            if (t.isNew()) {
                info("ATTEMPT. Persist domain [{}]", t.string());
                manager.persist(t);
            } else {
                info("ATTEMPT. Merge domain [{}]", t.string());
                manager.merge(t);
            }

            info("SUCCESS. Persisted (merged) domain [{}]", t.string());

            manager.getTransaction().commit();

            return t;
        } catch (Exception e) {
            Optional.ofNullable(manager.getTransaction()).ifPresent(EntityTransaction::rollback);
            error("FAILURE. Persist (merge) domain: [{}]", t.string());
            e.printStackTrace();
            return null;
        }
    }
}